﻿#region <!-- Using Directives -->

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;

using WordSharpDev.Extensions;
using WordSharpDev.Utils;

using Word = Microsoft.Office.Interop.Word;

#endregion

namespace WordSharpDev.Work
{
	internal static class VisualBuilder
	{
		public static void Build(Word.Document p_SourceCode)
		{
			var l_Exception = new ErrException("Could not build source code");

			foreach (Word.Table i_Table in p_SourceCode.Tables)
			{
				try
				{
					var l_ProjectType = i_Table.Descr.GetXmlString("./@Type");

					if (l_ProjectType.IsLikeFull("Project/Console")) { BuildProjectConsole(i_Table, p_SourceCode.Path); break; }
				}
				catch (Exception e)
				{
					l_Exception.AppendChildException(e);
				}
			}

			if (l_Exception.ChildExceptions.Count > 0) throw l_Exception;
		}

		private static void BuildProjectConsole(Word.Table p_ProjectCode, String p_ProjectPath)
		{
			var l_Tokens = GetTokens(p_ProjectCode);

			var l_Text1 = l_Tokens.OuterXml.FormatXml();

			ProcessLogicalSeparators(l_Tokens);

			var l_Text2 = l_Tokens.OuterXml.FormatXml();

			ProcessBinaryOperators(l_Tokens);

			var l_Text3 = l_Tokens.OuterXml.FormatXml();

			ProcessConsoleMethods(l_Tokens);

			var l_Text4 = l_Tokens.OuterXml.FormatXml();

			TransformPipeInstructions(l_Tokens);

			var l_Text5 = l_Tokens.OuterXml.FormatXml();

			//> Build main class

			var l_Main = "using System;";

			l_Main += "\r\n";

			l_Main += "\r\n" + "namespace WordSharpBuild";
			l_Main += "\r\n" + "{";

			l_Main += "\r\n" + "\t" + "internal class Program";
			l_Main += "\r\n" + "\t" + "{";
			l_Main += "\r\n" + "\t" + "\t" + "internal static void Main(String[] p_Args)";
			l_Main += "\r\n" + "\t" + "\t" + "{";

			l_Main += "\r\n" + CompileInstructions(l_Tokens).Indent("\t\t\t");

			l_Main += "\r\n" + "\t" + "\t" + "}";
			l_Main += "\r\n" + "\t" + "}";

			l_Main += "\r\n" + "}";

			l_Main.WriteFile(Path.Combine(p_ProjectPath, "Program.cs"));

			//> Build project file

			XmlNode l_Node;

			//> Project
			var l_Project = "<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' />".AsXmlNode();

			l_Project.SetAttribute("ToolsVersion", "4.0");
			l_Project.SetAttribute("DefaultTargets", "Build");

			//> Import
			var l_Import = l_Project.GetNewChild("Import");

			l_Import.SetAttribute("Project", @"$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props");
			l_Import.SetAttribute("Condition", @"Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')");

			//> PropertyGroup
			var l_Property = l_Project.GetNewChild("PropertyGroup");

			l_Node = l_Property.GetNewChild("Configuration");
			l_Node.SetAttribute("Condition", "'$(Configuration)' == ''");
			l_Node.InnerText = "Debug";

			l_Node = l_Property.GetNewChild("Platform");
			l_Node.SetAttribute("Condition", "'$(Platform)' == ''");
			l_Node.InnerText = "AnyCPU";

			l_Node = l_Property.GetNewChild("ProjectGuid");
			l_Node.InnerText = Guid.NewGuid().ToString().ToUpper();

			l_Node = l_Property.GetNewChild("OutputType");
			l_Node.InnerText = "Exe";

			l_Node = l_Property.GetNewChild("AppDesignerFolder");
			l_Node.InnerText = "Properties";

			l_Node = l_Property.GetNewChild("RootNamespace");
			l_Node.InnerText = "WordSharpBuild";

			l_Node = l_Property.GetNewChild("AssemblyName");
			l_Node.InnerText = "Program";

			l_Node = l_Property.GetNewChild("TargetFrameworkVersion");
			l_Node.InnerText = "v4.5";

			l_Node = l_Property.GetNewChild("FileAlignment");
			l_Node.InnerText = "512";

			//> PropertyGroup / Debug
			l_Property = l_Project.GetNewChild("PropertyGroup");
			l_Property.SetAttribute("Condition", "'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'");

			l_Node = l_Property.GetNewChild("PlatformTarget");
			l_Node.InnerText = "AnyCPU";

			l_Node = l_Property.GetNewChild("DebugSymbols");
			l_Node.InnerText = "True";

			l_Node = l_Property.GetNewChild("DebugType");
			l_Node.InnerText = "Full";

			l_Node = l_Property.GetNewChild("Optimize");
			l_Node.InnerText = "False";

			l_Node = l_Property.GetNewChild("OutputPath");
			l_Node.InnerText = @"bin\Debug\";

			l_Node = l_Property.GetNewChild("DefineConstants");
			l_Node.InnerText = "DEBUG;TRACE";

			l_Node = l_Property.GetNewChild("ErrorReport");
			l_Node.InnerText = "Prompt";

			l_Node = l_Property.GetNewChild("WarningLevel");
			l_Node.InnerText = "4";

			//> PropertyGroup / Release
			l_Property = l_Project.GetNewChild("PropertyGroup");
			l_Property.SetAttribute("Condition", "'$(Configuration)|$(Platform)' == 'Release|AnyCPU'");

			l_Node = l_Property.GetNewChild("PlatformTarget");
			l_Node.InnerText = "AnyCPU";

			l_Node = l_Property.GetNewChild("DebugType");
			l_Node.InnerText = "None";

			l_Node = l_Property.GetNewChild("Optimize");
			l_Node.InnerText = "True";

			l_Node = l_Property.GetNewChild("OutputPath");
			l_Node.InnerText = @"bin\Release\";

			l_Node = l_Property.GetNewChild("DefineConstants");
			l_Node.InnerText = "TRACE";

			l_Node = l_Property.GetNewChild("ErrorReport");
			l_Node.InnerText = "Prompt";

			l_Node = l_Property.GetNewChild("WarningLevel");
			l_Node.InnerText = "4";

			//> ItemGroup
			var l_ItemGroup = l_Project.GetNewChild("ItemGroup");

			l_Node = l_ItemGroup.GetNewChild("Reference");
			l_Node.SetAttribute("Include", "System");

			l_Node = l_ItemGroup.GetNewChild("Reference");
			l_Node.SetAttribute("Include", "System.Core");

			l_Node = l_ItemGroup.GetNewChild("Reference");
			l_Node.SetAttribute("Include", "System.Data");

			l_Node = l_ItemGroup.GetNewChild("Reference");
			l_Node.SetAttribute("Include", "System.Data.DataSetExtensions");

			l_Node = l_ItemGroup.GetNewChild("Reference");
			l_Node.SetAttribute("Include", "System.Xml");

			l_Node = l_ItemGroup.GetNewChild("Reference");
			l_Node.SetAttribute("Include", "System.Xml.Linq");

			l_Node = l_ItemGroup.GetNewChild("Reference");
			l_Node.SetAttribute("Include", "Microsoft.CSharp");

			//> ItemGroup
			l_ItemGroup = l_Project.GetNewChild("ItemGroup");

			l_Node = l_ItemGroup.GetNewChild("Compile");
			l_Node.SetAttribute("Include", "Program.cs");

			//> Import
			l_Import = l_Project.GetNewChild("Import");

			l_Import.SetAttribute("Project", @"$(MSBuildToolsPath)\Microsoft.CSharp.targets");

			l_Project.OuterXml.FormatXml().WriteFile(Path.Combine(p_ProjectPath, "Program.csproj"));

			//> MsBuild
			Root.RunMsBuild(Path.Combine(p_ProjectPath, "Program.csproj"));

			//> Run
			Process.Start(Path.Combine(p_ProjectPath, @"bin\Debug\Program.exe"));
		}

		#region <!-- GetTokens -->

		private static XmlNode GetTokens(Word.Table p_Code)
		{
			var l_Node = "<Tokens />".AsXmlNode();

			var l_Token = default(XmlNode);

			var l_Index = 0;

			var l_Pos = -1;

			while (true)
			{
				if (p_Code.GetLength() <= l_Index) break;

				var l_Character = p_Code.GetCharacter(l_Index);

				if (l_Character == " ")
				{
					l_Token = l_Node.GetNewChild("Token");

					l_Token.SetAttribute("Type", "Space");
				}
				else if (l_Character == "\t")
				{
					l_Token = l_Node.GetNewChild("Token");

					l_Token.SetAttribute("Type", "Tab");
				}
				else if (l_Character == "'")
				{
					l_Pos = FindNext(p_Code.Range.Text, l_Index + 1, '\'');

					l_Token = l_Node.GetNewChild("Token");

					if (0 <= l_Pos)
					{
						l_Token.SetAttribute("Type", "SingleQuotedExpr");
						l_Token.InnerText = p_Code.GetString(l_Index, l_Pos);

						l_Index = l_Pos;
					}
					else
					{
						l_Token.SetAttribute("Type", "SingleQuotedError");
						l_Token.InnerText = p_Code.GetString(l_Index);

						l_Index = p_Code.GetLength() - 1;
					}
				}
				else if (l_Character == "\"")
				{
					l_Pos = FindNext(p_Code.Range.Text, l_Index + 1, '\"');

					l_Token = l_Node.GetNewChild("Token");

					if (0 <= l_Pos)
					{
						l_Token.SetAttribute("Type", "DoubleQuotedExpr");
						l_Token.InnerText = p_Code.GetString(l_Index, l_Pos);

						l_Index = l_Pos;
					}
					else
					{
						l_Token.SetAttribute("Type", "DoubleQuotedError");
						l_Token.InnerText = p_Code.GetString(l_Index);

						l_Index = p_Code.GetLength() - 1;
					}
				}
				else if (l_Character == "|")
				{
					l_Token = l_Node.GetNewChild("Token");

					var l_Str2 = p_Code.GetString(l_Index, l_Index + 1);

					if (l_Str2 == "|>")
					{
						l_Token.SetAttribute("Type", "PipeRight");

						l_Index += 1;
					}
					else if (l_Str2 == "||")
					{
						l_Token.SetAttribute("Type", "DoublePipe");

						l_Index += 1;
					}
					else
					{
						l_Token.SetAttribute("Type", "Pipe");
					}
				}
				else if (l_Character == "\r")
				{
					l_Token = l_Node.GetNewChild("Token");

					l_Token.SetAttribute("Type", "CarriageReturn");
				}
				else if (l_Character == "\n")
				{
					l_Token = l_Node.GetNewChild("Token");

					l_Token.SetAttribute("Type", "LineFeed");
				}
				else if (l_Character.IsLikePartial(@"\w"))
				{
					l_Pos = FindNext(p_Code.Range.Text, l_Index + 1, (p_Char) => !p_Char.ToString().IsLikePartial(@"\w"));

					if (l_Index <= l_Pos)
					{
						l_Token.SetAttribute("Type", "Word");
						l_Token.InnerText = p_Code.GetString(l_Index, l_Pos);

						l_Index = l_Pos;
					}
					else
					{
						l_Token.SetAttribute("Type", "Error");
						l_Token.InnerText = p_Code.GetString(l_Index);

						l_Index = p_Code.GetLength() - 1;
					}
				}

				l_Index++;
			}

			return l_Node;
		}

		#endregion

		#region <!-- ProcessLogicalSeparators -->

		private static void ProcessLogicalSeparators(XmlNode p_Tokens)
		{
			while (true)
			{
				var l_End = true;

				foreach (XmlNode l_Node in p_Tokens.SelectNodes("//Token"))
				{
					var l_Type = l_Node.GetXmlString("./@Type");

					if (l_Type.IsLikeFull("Space|Tab|CarriageReturn|LineFeed"))
					{
						if (l_Node.ParentNode != null && l_Node.ParentNode.Name != "Separator")
						{
							if (l_Node.PreviousSibling != null && l_Node.PreviousSibling.Name == "Separator")
							{
								l_Node.ParentNode.RemoveChild(l_Node);
							}
							else
							{
								var l_Separator = p_Tokens.OwnerDocument.CreateElement("Separator");

								l_Node.ParentNode.InsertBefore(l_Separator, l_Node);

								l_Node.ParentNode.RemoveChild(l_Node);
							}

							l_End = false;
							break;
						}
					}
				}

				if (l_End) break;
			}
		}

		#endregion

		#region <!-- ProcessBinaryOperators -->

		private static void ProcessBinaryOperators(XmlNode p_Tokens)
		{
			while (true)
			{
				var l_End = true;

				foreach (XmlNode l_Node in p_Tokens.SelectNodes("//Token"))
				{
					var l_Type = l_Node.GetXmlString("./@Type");

					if (l_Type.IsLikeFull("PipeLeft|PipeRight"))
					{
						var l_Left = l_Node.PreviousSibling;

						while (l_Left != null && l_Left.Name == "Separator")
						{
							l_Left = l_Left.PreviousSibling;
						}

						if (l_Left == null)
						{
							l_Left = p_Tokens.OwnerDocument.CreateElement("Missing");
							l_Node.ParentNode.InsertBefore(l_Left, l_Node);
						}

						var l_Right = l_Node.NextSibling;

						while (l_Right != null && l_Right.Name == "Separator")
						{
							l_Right = l_Right.NextSibling;
						}

						if (l_Right == null)
						{
							l_Right = p_Tokens.OwnerDocument.CreateElement("Missing");
							l_Node.ParentNode.InsertAfter(l_Right, l_Node);
						}

						var l_Operator = p_Tokens.OwnerDocument.CreateElement("Operator");

						l_Operator.SetAttribute("Type", l_Type);

						l_Node.ParentNode.InsertBefore(l_Operator, l_Node);

						l_Left.SetNewParent(l_Operator);
						l_Right.SetNewParent(l_Operator);

						l_Node.ParentNode.RemoveChild(l_Node);

						l_End = false;
						break;
					}
				}

				if (l_End) break;
			}
		}

		#endregion

		#region <!-- ProcessConsoleMethods -->

		private static void ProcessConsoleMethods(XmlNode p_Tokens)
		{
			while (true)
			{
				var l_End = true;

				foreach (XmlNode l_Node in p_Tokens.SelectNodes("//Token"))
				{
					var l_Type = l_Node.GetXmlString("./@Type");

					if (l_Type.IsLikeFull("Word"))
					{
						if (l_Node.InnerText.IsLikeFull("WriteLine|ReadLine"))
						{
							var l_Method = l_Node.OwnerDocument.CreateElement("Method");

							l_Method.SetAttribute("Target", "Static");

							l_Method.SetAttribute("Type", "global::System.Console");

							l_Method.SetAttribute("Name", l_Node.InnerText);

							l_Node.ParentNode.InsertBefore(l_Method, l_Node);

							l_Node.ParentNode.RemoveChild(l_Node);

							l_End = false;
							break;
						}
					}
				}

				if (l_End) break;
			}
		}

		#endregion

		#region <!-- TransformPipeInstructions -->

		private static void TransformPipeInstruction(XmlNode p_Node)
		{
			XmlNode l_Instruction;

			var l_Left = p_Node.FirstChild;
			var l_Right = p_Node.LastChild;

			if (l_Left == null) { p_Node.SetAttribute("Error", "Missing left operand"); return; }
			if (l_Right == null) { p_Node.SetAttribute("Error", "Missing right operand"); return; }

			if (l_Left.Name == "Operator" && l_Left.GetXmlString("./@Type").IsLikeFull("PipeLeft|PipeRight")) { TransformPipeInstruction(l_Left); return; }
			if (l_Right.Name == "Operator" && l_Right.GetXmlString("./@Type").IsLikeFull("PipeLeft|PipeRight")) { TransformPipeInstruction(l_Right); return; }

			if (p_Node.GetXmlString("./@Type") == "PipeRight" && l_Right.Name == "Method" && l_Right.GetXmlString("./@Target") == "Static" && l_Right.GetXmlString("./@Name") == "WriteLine")
			{
				l_Instruction = p_Node.OwnerDocument.CreateElement("Instruction");

				l_Instruction.InnerText = String.Format("{0}.{1}({2})", l_Right.GetXmlString("./@Type"), l_Right.GetXmlString("./@Name"), l_Left.InnerText);

				p_Node.ParentNode.InsertBefore(l_Instruction, p_Node);

				p_Node.ParentNode.RemoveChild(p_Node);
			}
			else if (p_Node.GetXmlString("./@Type") == "PipeRight" && l_Right.Name == "Method" && l_Right.GetXmlString("./@Target") == "Static" && l_Right.GetXmlString("./@Name") == "ReadLine")
			{
				l_Instruction = p_Node.OwnerDocument.CreateElement("Instruction");

				l_Instruction.InnerText = String.Format("{0}", l_Left.InnerText);

				p_Node.ParentNode.InsertBefore(l_Instruction, p_Node);

				l_Instruction = p_Node.OwnerDocument.CreateElement("Instruction");

				l_Instruction.InnerText = String.Format("{0}.{1}()", l_Right.GetXmlString("./@Type"), l_Right.GetXmlString("./@Name"));

				p_Node.ParentNode.InsertBefore(l_Instruction, p_Node);

				p_Node.ParentNode.RemoveChild(p_Node);
			}
		}

		private static void TransformPipeInstructions(XmlNode p_Tokens)
		{
			while (true)
			{
				var l_End = true;

				foreach (XmlNode l_Node in p_Tokens.SelectNodes("//Operator"))
				{
					var l_Type = l_Node.GetXmlString("./@Type");

					if (l_Type.IsLikeFull("PipeLeft|PipeRight"))
					{
						TransformPipeInstruction(l_Node);

						l_End = false;
						break;
					}
				}

				if (l_End) break;
			}
		}

		#endregion

		#region <!-- CompileInstructions -->

		private static String CompileInstructions(XmlNode p_Tokens)
		{
			var l_Result = "";
			var l_Separator = "";

			foreach (XmlNode l_Node in p_Tokens.SelectNodes("//Instruction"))
			{
				l_Result += l_Separator + l_Node.InnerText + ";";

				l_Separator = "\r\n";
			}

			return l_Result;
		}

		#endregion

		#region <!-- FindNext -->

		private static Int32 FindNext(String p_Source, Int32 p_Start, Char p_Char)
		{
			var l_Result = -1;

			if (p_Source.IsVoid()) return l_Result;

			var l_Index = p_Start % p_Source.Length;

			var l_Esc = 0;

			while (true)
			{
				if (p_Source.Length <= l_Index) break;

				var l_Char = p_Source[l_Index];

				if (l_Char == '\\') l_Esc++; else l_Esc = 0;

				if (l_Char == p_Char && l_Esc % 2 == 0) return l_Index;

				l_Index++;
			}

			return l_Result;
		}

		private static Int32 FindNext(String p_Source, Int32 p_Start, Func<Char, Boolean> p_EndingCondition)
		{
			if (p_Source.IsVoid()) return -1;

			var l_Index = p_Start % p_Source.Length;

			var l_Esc = 0;

			while (true)
			{
				if (p_Source.Length <= l_Index) break;

				var l_Char = p_Source[l_Index];

				if (l_Char == '\\') l_Esc++; else l_Esc = 0;

				if (p_EndingCondition(l_Char) && l_Esc % 2 == 0) return l_Index - 1;

				l_Index++;
			}

			return p_Source.Length - 1;
		}

		#endregion
	}
}